﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security.Policy;
using System.ServiceModel;
using System.Text;
using System.Threading.Tasks;
using Ionic.Zip;
using Microsoft.Practices.Unity;
using Sern.Abstracts.Common;
using System.IO.Compression;
using Sern.Abstracts.Tracker.DataContracts;
using Sern.Abstracts.Tracker.ServiceContracts;
using Sern.Common;

namespace Sern.ServiceImplementation.MapReduce
{
    public class MapReduceHostService : IMapReduceHost, IDisposable
    {
        private string _hostName;
        private Listener _listener;
        private readonly IDictionary<Guid, AppDomainClient> _activeClients = new Dictionary<Guid, AppDomainClient>();
        

        public string MapReduceHostUri { get
        {
            var bindingInfo = ComponentLocator.Unity.Resolve<IBindingFactory>()[ClientType.MapReduceHost]();
            var builder = new UriBuilder(bindingInfo.Scheme, _hostName, bindingInfo.Port ?? -1, ClientType.MapReduceHost.ToString() );
            return builder.Uri.AbsoluteUri;
        } }

        public MapReduceHostService(string hostName)
        {
            
            _hostName = hostName;
                        
            //StartListening();
        }

        [Obsolete("Let the WCF host do this, don't do it from inside the class.", true)]
        private void StartListening()
        {
            var bindingInfo = ComponentLocator.Unity.Resolve<IBindingFactory>()[ClientType.MapReduceHost]();
            var _serviceHost = ComponentLocator.Unity.Resolve<IListenerFactory>().Create(
                new UriBuilder(bindingInfo.Scheme, _hostName, bindingInfo.Port ?? -1).Uri,
                new Uri(ClientType.MapReduceHost.ToString(), UriKind.Relative),
                ClientType.MapReduceHost,
                this);
            _serviceHost.Open();
        }

        internal class AppDomainClient
        {
            public AppDomain AppDomain { get; set; }
            public IClientInstance ClientInstance { get; set; }
        }

        

        public string HostName { get; private set; }

        public DirectoryInfo CurrentDirectory
        {
            get
            {
                var currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory());
                return currentDirectory;
            }
        }

        public void UploadClient(Guid packageId, byte[] installerPackage)
        {                        
            var packageFile = new FileInfo(CurrentDirectory.FullName + "\\" + packageId.ToString("N") + ".zip");

            using(FileStream fs = new FileStream(packageFile.FullName, FileMode.Append))
            {
                fs.Write(installerPackage,0,installerPackage.Length);
            }
        }


        public void Register(Guid packageId, Guid trackerId, string trackerUri)
        {
            var clientBinaryZipFileName = CurrentDirectory.FullName + @"\" + packageId.ToString("N") + ".zip";
            
            //Create output folder
            var outputFolder = new DirectoryInfo(CurrentDirectory.FullName + @"\" + packageId.ToString("N"));
            if(!outputFolder.Exists)
                outputFolder.Create();

            using(var binaryZip = ZipFile.Read(clientBinaryZipFileName))
            {                
                foreach(var entry in binaryZip)
                {
                    entry.Extract(outputFolder.FullName, ExtractExistingFileAction.OverwriteSilently);
                }
            }

            var entryFile = outputFolder
                .GetFiles("*.Main.dll",SearchOption.AllDirectories).Single();
            //We can go fancy and using an xml manifest file etc. but this should do - for now. Basically the class must be called Entry!
            var entryTypeName = entryFile.Name.Substring(0, entryFile.Name.LastIndexOf(".dll", StringComparison.OrdinalIgnoreCase)) + ".Entry";

            IClientInstance remoteClientInstance;
            AppDomain newRemoteAppDomain;
            RemoteLoadType(packageId, entryFile, entryTypeName, out remoteClientInstance, out newRemoteAppDomain);

            remoteClientInstance.Initialize(
                new ClientInitializationArgument
                    {
                        ClientId = packageId,
                        HostName =  HostName,
                        JobId = trackerId,
                        TrackerUri = trackerUri,
                        LogLevel = LogLevel.Trace,
                        MapReduceHostUri =  MapReduceHostUri
                    });
            
            _activeClients.Add(
                packageId,
                new AppDomainClient
                    {
                        AppDomain = newRemoteAppDomain,
                        ClientInstance = remoteClientInstance
                    });

            
        }

        public static void RemoteLoadType(Guid clientId, FileInfo entryFile, string entryTypeName, out IClientInstance mapReduceClient, out AppDomain remoteDomain )
        {
            Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence);
            AppDomainSetup setup = AppDomain.CurrentDomain.SetupInformation;
            
            AppDomain newDomain = AppDomain.CreateDomain(clientId.ToString("N"), evidence, setup);

            var remoteInstance = (IClientInstance)newDomain.CreateInstanceFrom(entryFile.FullName, entryTypeName).Unwrap();

            mapReduceClient = remoteInstance;
            remoteDomain = newDomain;
        }

        public void Deregister(Guid packageInstanceId)
        {
            _activeClients[packageInstanceId].ClientInstance.Deregister();
            AppDomain.Unload(_activeClients[packageInstanceId].AppDomain); 
            _activeClients.Remove(packageInstanceId);
        }

        public void Dispose()
        {
            StopListening();

            foreach(var key in _activeClients.Keys)
            {
                Deregister(key);
            }
            
        }

        private void StopListening()
        {
            _listener.Close();
        }
    }
}
